REQ-70 file family: do not route — the gap is a security bypass - #72
Merged
Conversation
… bypass
REQ-70 family routing, file family: investigated, and DELIBERATELY NOT ROUTED.
The json precedent held — the 8 riina_builtin_fail_* C functions exist and
have never executed, because builtin_canonical does not route the family. But
the reason they must not simply be wired up is different in kind from the
correctness divergences json, masa, simpan and net produced.
builtins::fail does not touch the host filesystem directly. All eight builtins
first call gate_read/gate_write/gate_delete, which evaluate the Coq-modeled
can_read/can_write predicates against an inode model (owner uid/gid, mode 0644
on first touch) and the AccessContext set by vfs_jadi_pengguna. Twelve gate
call sites in that file.
The emitted C helpers contain ZERO gating constructs. fail_baca is a bare
fopen; fail_tulis a bare fwrite. Measured:
builtins/fail.rs gate_read|gate_write|gate_delete -> 12 call sites
emit.rs, all 8 gate|can_read|can_write|denied|
permission|inode|owner -> 0 occurrences
So adding a fail/vfs arm to builtin_canonical would mean `riinac run` denies an
access that `riinac build` permits: the compiler becomes the way around a
verified security check. That is REQ-27 enforcement parity failing in the
direction that matters. Demonstrated on the interpreter — uid 1 writes a file,
uid 2 is refused with "permission denied (verified can_write is false for the
current uid)" and the file on disk is unchanged.
Prerequisite for routing, recorded in the REQ-70 row: port the inode model,
the uid/gid context and the two predicates into the emitted runtime, then route
fail and vfs TOGETHER — vfs has no C helpers at all today, so routing it alone
fails in the C compiler.
Adds file_gate_parity.rs, written as an INVARIANT rather than a
"stays unrouted" pin: a compiled binary must not perform an access the
interpreter refuses. It passes today (vacuously — no binary exists), passes
after a correct gate port, and fails only if someone routes the family without
porting. It therefore does not have to be deleted to make progress, unlike a
pin on the current routing state, which is the friction my masa exclusion
created for the session that later resolved it properly in #70.
Honest limitation, also stated in the test: the SECURITY REGRESSION branch is
reviewed but UNEXECUTED. Reaching it needs a compiled binary that switches uid
and writes, which cannot be built today because emit.rs has no
riina_builtin_vfs_* functions. Two controls were run and both correctly held
vacuously: routing `fail` alone (no way to switch uid, so no bypass is
expressible) and routing `fail`+`vfs` (fails in the C compiler). The assertion
acquires teeth exactly when someone writes the vfs C helpers — the moment the
bypass becomes shippable.
Verified: 03_PROTO 3323/0 (+1), clippy clean, audit-docs.sh 0 discrepancies.
…e guard Regenerated by the pre-push riinac verify --full at 2536f0b. Mode fast -> full. main carried a fast-mode record (Rust tests + clippy only, scope line 'NOT a proof-checked verification'); this replaces it with a full-mode one that re-compiles the primary proof lane: 331 .vo, 0 admits, 0 axioms, 5 capstones kernel-attested, funext the only whitelisted axiom. Strictly more attested than what it replaces, which is the direction this file is allowed to move. Rust tests 3323 (+1, file_gate_parity.rs). TLA+/Alloy read PASS for the same container-local reason recorded at the #68/#69 chains — the pinned jars are on disk here; on an unprovisioned clone they return to WARN. Isabelle and F* still WARN, correctly: both are RETIRED, and per the Wave 0.3 finding a retired lane should not be demanding tooling at all.
ib823
marked this pull request as ready for review
August 18, 2026 20:58
ib823
pushed a commit
that referenced
this pull request
Aug 18, 2026
Metrics unchanged in substance (3323 tests, 12678 Qed, 0 admits, 0 axioms); timestamp, commit stamp and the rebuilt WASM asset only. The published count is correct this round — the fail-closed guard added after the #69 chain (where the deploy prebuild silently published 2659 for a true 3284) did its job. dim1_dim9 promotion_ready reads true for the same container-local reason recorded at the #68/#69 chains: the pinned tla2tools/alloy jars are on disk here. Top-line verdict unchanged: overall_foundation FAIL.
ib823
pushed a commit
that referenced
this pull request
Aug 18, 2026
…te fail/vfs REQ-70. The file family was held back because routing it would have let a compiled binary bypass a Coq-modeled access-control check that riinac run enforces (12 gate call sites in the interpreter, 0 gating constructs in the C helpers). The gate is now mirrored, so the family routes. DESIGN, recorded because the obvious approach is impossible. Emitted C cannot call into Rust: the pipeline is `cc -o out one.c` with nothing linked, so a single shared implementation callable from both backends would mean shipping per-target Rust staticlibs (native/wasm32/android/ios) and rebuilding the compile pipeline. Instead the COQ MODEL is the single source of truth — domains/VerifiedFileSystem.v (Inode/Ownership/Permission/is_owner/ get_permission) — with riina-os/src/vfs.rs and the emitted C as two implementations of it, held together by a differential. Same shape as the masa civil calendar and the GF128/AES Coq-Rust equivalences. The C mirrors: the inode table, first-touch ownership at mode 0644 (owner rw, group/other r), owner > group > other resolution, can_read/can_write as is_root OR the applicable bit, and gate_delete clearing the mapping so a re-creator owns the file. Denial exits non-zero with the interpreter's wording and cannot fall through to the filesystem. Matched op-for-op, including the negative space: fail_ada and fail_senarai are UNGATED in the interpreter (an existence check is not an access), so they are ungated in C too. Gating them would have been a divergence in the opposite direction — denying what the interpreter allows. Deliberately NOT the host OS's own permission bits: the gate is RIINA's model and must deny where the model denies even when the host would allow (the process typically owns these files), so letting the kernel decide would silently make the check a no-op. Routed: the 8 fail_* builtins plus vfs_mula/vfs_jadi_pengguna, which are what make the gate meaningful. vfs_tulis/vfs_baca/vfs_padam stay interpreter-only — they operate on the in-memory VirtualFs with quota accounting that has no C implementation, and stubbing them would claim an enforcement this backend cannot make. THE #72 LIMITATION IS NOW CLOSED. file_gate_parity.rs's active SECURITY REGRESSION arm was previously unexecutable — no vfs C helpers existed to build the bypass with — and is now negative-controlled: deleting a single riina_gate call from the emitted fail_tulis makes the compiled binary perform the write (exit 0) and the test fails with its intended message. Adds file_differential.rs (7 cases), chosen to separate the resolution arms rather than to demonstrate the happy path: owner write; non-owner read ALLOWED at 0644; non-owner write and append DENIED; delete clears ownership so a different uid may re-create and own; ungated existence check. A C gate that collapsed owner/group/other by always consulting perm_owner would pass a naive same-uid test and fail these. Also updates pkg_build's interpreter-only boundary example from fail_baca (now compiles) to vfs_baca — a deliberate choice rather than the next arbitrary one, since the VirtualFs quota has no C implementation and so it should stay interpreter-only for as long as that holds. The test's own comment asks for exactly this update. Verified: 03_PROTO 3330/0 (+7), 05_TOOLING 323/0, clippy clean on both, audit-docs.sh 0 discrepancies. STDLIB.md regenerated from the compiler: fail_* and the two setters native-only, the VirtualFs trio still interp-only.
14 tasks
ib823
added a commit
that referenced
this pull request
Aug 20, 2026
… fail/vfs The file family was held back in #72 because routing it would have let a compiled binary bypass a Coq-modeled access-control check that riinac run enforces (12 gate call sites in the interpreter, 0 in the C helpers). The gate is now mirrored, so the family routes. Design: emitted C cannot call into Rust — the pipeline is `cc -o out one.c` with nothing linked — so a single shared implementation would have meant per-target Rust staticlibs. Instead the Coq model is the single source of truth (domains/VerifiedFileSystem.v), with riina-os/src/vfs.rs and the emitted C as two implementations of it, held together by a differential. Same shape as the masa civil calendar and the GF128/AES Coq-Rust equivalences. Matched op-for-op including the negative space: fail_ada and fail_senarai are ungated in the interpreter, so they are ungated in C — gating them would be a divergence in the opposite direction. Routed: 8 fail_* plus vfs_mula/vfs_jadi_pengguna. The VirtualFs trio stays interpreter-only; its quota accounting has no C implementation and stubbing it would claim an enforcement this backend cannot make. The #72 limitation is closed: file_gate_parity.rs's active SECURITY REGRESSION arm was previously unexecutable and is now negative-controlled — deleting one riina_gate call makes the compiled binary perform the forbidden write and the test fails. Adds file_differential.rs (7 cases) chosen to separate the owner/group/other resolution arms rather than demonstrate the happy path. Also records the keselamatan inspection: its taint discipline is compile-time (verified by running riinac check), only 3 of 42 have a runtime property, and those call the same gate — so this PR covers them too. 3330/0 tests in 03_PROTO, 323/0 in 05_TOOLING, clippy clean, 0 doc discrepancies.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Investigated the file family and deliberately did not route it. The
jsonprecedent held — the 8riina_builtin_fail_*C functions exist and have never executed — but the reason they must not simply be wired up is different in kind from the correctness divergencesjson,masa,simpanandnetproduced.The finding
builtins::faildoes not touch the host filesystem directly. All eight builtins first callgate_read/gate_write/gate_delete, which evaluate the Coq-modeledcan_read/can_writepredicates against an inode model (owner uid/gid, mode 0644 on first touch) and theAccessContextset byvfs_jadi_pengguna.builtins/fail.rs—gate_read/gate_write/gate_deleteemit.rs, all 8 helpers —gate/can_read/can_write/denied/permission/inode/ownerfail_bacais a barefopen;fail_tulisa barefwrite.So adding a
fail/vfsarm tobuiltin_canonicalwould meanriinac rundenies an access thatriinac buildpermits — the compiler becomes the way around a verified security check. That is REQ-27 enforcement parity failing in the direction that matters.Measured on the interpreter:
Prerequisite for routing, recorded in the REQ-70 row
Port the inode model, the uid/gid context, and the two predicates into the emitted runtime — then route
failandvfstogether.vfshas no C helpers at all today, so routing it alone fails in the C compiler.What this PR adds
crates/riinac/tests/file_gate_parity.rs, written as an invariant rather than a "stays unrouted" pin:It therefore never has to be deleted to make progress. That shape is deliberate: my earlier
masaexclusion used a "stays unrouted" pin, and #70 had to remove it when it resolved the semantics properly. An invariant does not create that friction.Honest limitation
The
SECURITY REGRESSIONbranch is reviewed but UNEXECUTED. Reaching it needs a compiled binary that switches uid and then writes, which cannot be built today:emit.rshas noriina_builtin_vfs_*functions, so routingvfsemits C that does not compile.Two controls were run, and both correctly held vacuously:
failalone — no way to switch uid, so no bypass is expressible;fail+vfs— fails in the C compiler.The assertion is verified in its vacuous arm and acquires teeth exactly when someone writes the
vfsC helpers, which is the moment the bypass becomes shippable. Stated in the test's own docs too, not just here.Type
.riifileTesting
cargo test --all(03_PROTO): 3323/0 (+1)cargo clippy --all-targets -- -D warningscleanaudit-docs.sh0 discrepanciesfile_gate_parity.rsChecklist
unsafeAdmittedin Coq proofs (no proof changes)Note
Four families have now been routed (
json,masa,simpan,net) and every one exposed a defect the "lowers to C" Backend column hid. This is the fifth, and the first where the hidden defect is a security property rather than a wrong answer — which suggestskeselamatan(42 security sinks, still unrouted) deserves the same look before anyone routes it.Generated by Claude Code